#include <RenderSubSystem/cvIRender.h>
#include <RenderSubsystem/Renderable.h>
#include <RenderSubSystem/cvIMRenderer/LineBatch.h>
#include <Logger/GlobalLogger.h>
#include <Exceptions/OpenGLException.h>
#include <cmath>

namespace cvImmediateModeRenderer
{
	cvPool LineBatch::m_pool(sizeof(LineBatch));
	const size_t LINEBATCH_ALLOC = 4096;		// allocations are multiple to count. bigger, more memory is allocated at time.

	LineBatch * LineBatch::create()
	{
		LineBatch * batch = ::new(m_pool.malloc()) LineBatch;
		batch->m_singleshot = false;
		return batch;
	}

	LineBatch * LineBatch::createSingleshot()
	{
		LineBatch * batch = ::new(m_pool.malloc()) LineBatch;
		batch->m_singleshot = true;
		batch->m_singleshot_target = Line::createSingleshot();
		return batch;
	}

	LineBatch::LineBatch() :
		m_storgatearray(0), m_primitive_cnt(0),
		m_is_storgatedirty(false), m_trigger_sort(false),
		m_singleshot_target(NULL), m_locked_renderlist(0)
	{
		m_storgatearray.reserve(LINEBATCH_ALLOC);
		m_do_sort = true;
	}

	void LineBatch::destroy()
	{
		this->~LineBatch();
		m_pool.free(this);
	}

	//void LineBatch::operator delete(void * ptr)	{ m_pool.free(ptr); }

	LineBatch::~LineBatch()
	{
		destroy_list();
		m_storgatearray.clear();
		if(m_singleshot_target)	{ m_singleshot_target->destroy(); }
	}

	void LineBatch::purgeMemoryPool()
	{
		m_pool.purge_memory();
	}

	void LineBatch::enableSort(bool state)	{ m_do_sort = true; }

	bool LineBatch::sort()
	{
		using cvRenderInterface::renderinglist_t;
		using cvRenderInterface::Renderable;
		if(m_do_sort || m_trigger_sort)
		{
			m_is_storgatedirty = true;
			m_trigger_sort = false;
			return CollectionNode::sort();
		}
		return false;
	}

	const Collection LineBatch::collectionType() const	{ return  PRIMITIVE_LINE; }

	cvRenderInterface::Renderable * LineBatch::writeRenderable()
	{
		if(m_lockedtransfer && m_storgatearray.size() > 0)
		{	// ignore writing into collection while it's locked
			return m_singleshot_target;
		}
		else
		{
			__reserve(1);
			m_storgatearray.push_back(line_struct());
			m_singleshot_target->remapStorgate(this, NULL, &m_storgatearray.back());
			m_singleshot_target->color(cvRenderInterface::IRenderer::DrawColour);
			return m_singleshot_target;
		}
	}

	void LineBatch::fetchNode(cvRenderInterface::Renderable * prim)
	{
		__reserve(1);
		m_storgatearray.push_back(line_struct());
		prim->remapStorgate(this, prim->getStorgatePtr(), &m_storgatearray.back());
		m_collectionlist.push_back(*prim);
		++m_primitive_cnt;
		m_trigger_sort = true;
		m_is_storgatedirty = true;
	}

	size_t LineBatch::__alloc_chunk_size(size_t minrequest)
	{	// calculate new size: grow linearly plus logarithmic increase times minimum allocation count.
		// oldsz * 2 + (round(log(oldsz*2)) * LINEBATCH_ALLOC - oldsz)
		size_t requestsize = std::ceil(float(minrequest) / LINEBATCH_ALLOC);
		float accelgrow = std::log(requestsize);
		float round = std::modf(accelgrow, &accelgrow);
		if(round >= 0.5f)	{ accelgrow += 1.0f; }
		else if(accelgrow < 1.0f)	{ accelgrow = 1.0f; }
		requestsize = (requestsize * (size_t)accelgrow) * LINEBATCH_ALLOC;
		return requestsize;
	}

	void LineBatch::__reserve(size_t size)
	{	using cvRenderInterface::renderinglist_t;
		if(!m_singleshot)
		{
			size_t minrequest = m_collectionlist.size() + size;
			if(minrequest > m_storgatearray.capacity())
			{
				LOGINFO("LineBatch(): Reserving storgate!");
				if(!m_collectionlist.empty())
				{	// Have to remap existing nodes.
					__remap(__alloc_chunk_size(minrequest), true);
				}
				else
				{
					m_storgatearray.reserve(__alloc_chunk_size(minrequest));
				}
			}
		}
		else
		{
			size_t minrequest = m_storgatearray.size() + size;
			if(minrequest > m_storgatearray.capacity())
			{
				m_storgatearray.reserve(__alloc_chunk_size(minrequest));
			}
		}
	}

	void LineBatch::__remap(size_t reservespace, bool force_realloc)
	{	using cvRenderInterface::renderinglist_t;
		if(m_is_storgatedirty && !m_collectionlist.empty())
		{

			renderinglist_t::iterator it = m_collectionlist.begin();
			size_t i;
			Line * prim;

			if(force_realloc || m_collectionlist.size() > m_storgatearray.capacity())
			{	// need more capacity. map all nodes into fresh buffer.
				structarray_t tmp_storgatebuffer;	// init tmp_storgatebuffer
				tmp_storgatebuffer.reserve(reservespace);
				tmp_storgatebuffer.resize(m_collectionlist.size());

				// copy primitives from m_storgatearray into tmp_storgatebuffer
				i = 0;
				while(it != m_collectionlist.end())
				{
					prim = static_cast<Line*>(&*it);
					prim->remapStorgate(this, prim->getStorgatePtr(), &tmp_storgatebuffer[i]);
					++it;
					++i;
				}

				m_storgatearray.swap(tmp_storgatebuffer);	// swap tmp_storgatebuffer into m_storgatearray
				m_is_storgatedirty = false;
			}	// ~tmp_storgatebuffer();
			else
			{
				// map incoming nodes into existing buffer. existing nodes aren't touched.
				line_struct * ptr;
				size_t dirty_index = 0;
				i = m_storgatearray.size();
				m_storgatearray.resize(m_collectionlist.size());
				while(i < m_collectionlist.size() && it != m_collectionlist.end())
				{
					prim = &static_cast<Line&>(*it);
					ptr = reinterpret_cast<line_struct*>(prim->getStorgatePtr());
					if(ptr < &m_storgatearray[0] || ptr > &m_storgatearray[i])
					{	// node didn't point into m_storgatearray's space, remap it:
						prim->remapStorgate(this, ptr, &m_storgatearray[i]);
						++i;
					}
					else if(ptr != &m_storgatearray[dirty_index])
					{	// some existing node were removed or drawingorder was changed.
						m_is_storgatedirty = true;
					}
					++it;
					++dirty_index;
				}
			}

		}
	}

	void LineBatch::fetchNodes(cvRenderInterface::renderinglist_it_t begin,
							   cvRenderInterface::renderinglist_it_t end)
	{	using cvRenderInterface::renderinglist_t;

		append_range(begin, end);

		// compile storgate
		if(m_do_sort)
		{	// can't use CollcetionNode::sort() because it recureses to other nodes!
			m_collectionlist.sort(&cvRenderInterface::compare_drawnodes);
			m_is_storgatedirty = true;
			m_trigger_sort = false;
		}
		m_primitive_cnt = m_collectionlist.size();
		__reserve(m_primitive_cnt);		// reserve
		__remap(0);

		// Remove range from mainlist and destroy spliced collections
		m_layer->renderList.erase_and_dispose(begin, end, cvRenderInterface::renderinglist_element_disposer());
	}

	void LineBatch::detachNode(cvRenderInterface::Renderable * prim)
	{	using cvRenderInterface::renderinglist_t;
		m_collectionlist.erase(renderinglist_t::s_iterator_to(*prim));
		m_is_storgatedirty = true;
		--m_primitive_cnt;
	}

	void LineBatch::processCollect(const cvObjects::BaseCameraObject * camera)
	{	// Nothing. YET. :P
		// rehash the line read-write primitives to spacehash.
	}

	void LineBatch::processTransfer()
	{
		if((!m_singleshot) && m_is_storgatedirty)
		{
			__remap(0, true);
		}
	}

	void LineBatch::lockTransfer(bool enable)
	{
		if(enable)
		{
			processTransfer();
			line_struct * p = &m_storgatearray[0];
			m_locked_renderlist = glGenLists(1);
			glNewList(m_locked_renderlist, GL_COMPILE);
				glEnableClientState(GL_VERTEX_ARRAY);
				glEnableClientState(GL_COLOR_ARRAY);
				glVertexPointer(2, GL_FLOAT, sizeof(cvVert) + sizeof(RGBAf), &(p->v1));
				glColorPointer(4, GL_FLOAT, sizeof(cvVert) + sizeof(RGBAf), &(p->c1));
				glDrawArrays(GL_LINES, 0,  m_storgatearray.size() * 2);	// two vertexes per line.
				glDisableClientState(GL_VERTEX_ARRAY);
				glDisableClientState(GL_COLOR_ARRAY);
			glEndList();
		}
		else
		{
			if(m_locked_renderlist)	{ glDeleteLists(m_locked_renderlist, 1); }
			m_locked_renderlist = 0;
		}
		m_lockedtransfer = enable;
	}

	void LineBatch::processRender()
	{
		if(!m_lockedtransfer)
		{
			line_struct * p = &m_storgatearray[0];
			glEnableClientState(GL_VERTEX_ARRAY);
			glEnableClientState(GL_COLOR_ARRAY);
			glVertexPointer(2, GL_FLOAT, sizeof(cvVert) + sizeof(RGBAf), &(p->v1));
			glColorPointer(4, GL_FLOAT, sizeof(cvVert) + sizeof(RGBAf), &(p->c1));
			glDrawArrays(GL_LINES, 0,  m_storgatearray.size() * 2);	// two vertexes per line.
			glDisableClientState(GL_VERTEX_ARRAY);
			glDisableClientState(GL_COLOR_ARRAY);

			if(m_singleshot)
			{	// flush nodes and buffers in singleshot mode:
				m_collectionlist.clear();
				m_storgatearray.clear();
			}
		}
		else
		{
			glCallList(m_lockedtransfer);
		}
	}
};
